package com.qf.index;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;

import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ReadKw {

    public static void main(String[] args) throws SQLException {

        /*
          读取mesh表，保存进keyWordAndMeshMap（此map中的款目词作了标准化处理，参与截词法提取关键词）和originalAndMeshMap（此map中的款目词仅是转换成了小写），并将上一步中获取的关键词及数量（keyWordSumMap）进行插表（keyWordSumTable）。
          注意事项：
          1. 关键词出现多少次才可以认定为关键词呢？在测试程序中大于0即可，实际使用时，以大于50次为标准，设置在方法：insertKeyWordSumMap中（if (sum > 0 || (keyWordList.size() == 2 && sum > 0) || (keyWordList.size() == 3 && sum > 0)) {），
          2. keyWordList.size() == 2即是全写与缩写，数量可以设置为10次。keyWordList.size() == 3即是mesh词，数量可以设置为1次。
         */

        String newDataName = "scholar_25_01.";
        String summaryTable = newDataName + "pm_ut_aid_info_0120";
        String spiderCnCoreTable = newDataName + "spider_cn_core";
        String spiderCnKiAnalysisTable = newDataName + "spider_cnki_analysis";
        String spiderCqVipAnalysisTable = newDataName + "spider_cqvip_analysis";
        String spiderWanFangAnalysisTable = newDataName + "spider_wanfang_analysis";
        String spiderYiiGleAnalysisTable = newDataName + "spider_yiigle_analysis";
        String pmArticleTable = newDataName + "spider_pubmed_vip_extend";
        String utArticleTable = newDataName + "spider_tb_true_92007022_1125";
        String jourMergeTable = newDataName + "basic_jour_merge";
        String quotaTable = newDataName + "basic_core_paper_quota";
        String basicCoreTable = newDataName + "basic_core_paper";
        String wordAndCodeSetMapTable = newDataName + "word_and_code_set_map";
        String fullAbbrKeyWordTable = newDataName + "full_abbr_key_word_0124";
        String basicDeptTable = "scholar_25_01.tb_true_china_hosp_6_year_dept";
        String keyWordSumTable = newDataName + "key_word_sum_0124";
        String meshTable = newDataName + "help_mesh_tree_gcr_0615";
        String cnBasicDeptTable = newDataName + "cn_dept_sum_dy30";
        String basicUnivCollTable = newDataName + "basic_univ_coll";
        String htLocationChinaTable = newDataName + "ht_location_china";
        String basicHospUnivDeptTable = newDataName + "basic_hosp_or_univ_dept";
        String hospUnivInfoSumTable = newDataName + "hosp_or_univ_info_sum";
        Map<String, Integer> termAndSumMap = new HashMap<>();

        int onePageNum = 1000;
//        String limit = "";
        String limit = " limit 200";

        readKeyWord(summaryTable, onePageNum, limit, "id", "title`,`keyword`,`ab`,`cn_kw`,`cn_ab`,`aid`,`pmId`,`ut`,`c1_au`,`rp_au`,`language", true, 10, fullAbbrKeyWordTable, keyWordSumTable,
                meshTable, htLocationChinaTable, basicHospUnivDeptTable, hospUnivInfoSumTable, termAndSumMap);

    }

    public static Map<Integer, byte[]> readKeyWord(String summaryTable, int onePageNum, String limit, String autoId, String field, boolean print, int allTotal, String fullAbbrKeyWordTable,
                                                   String keyWordSumTable, String meshTable, String htLocationChinaTable, String basicHospUnivDeptTable, String hospUnivInfoSumTable, Map<String, Integer> termAndSumMap) throws SQLException {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        int tableMax = Db.use().queryNumber("select max(" + autoId + ") from " + summaryTable).intValue();
        if (tableMax < 1) {
            return new HashMap<>();
        }
        Db.use().execute("truncate table " + fullAbbrKeyWordTable);
        Db.use().execute("truncate table " + keyWordSumTable);
        Map<String, String> languageMap = uSet.getLanguageMap();
        Map<String, String> reKwMap = cList.getReKwMap();
        Map<String, String> lowerToUpperMap = new ConcurrentHashMap<>();
        Map<List<String>, Integer> keyWordSumMap = new ConcurrentHashMap<>();
        Map<String, Integer> cnKeyWordSumMap = new ConcurrentHashMap<>();
        Map<String, String> md5Map = new ConcurrentHashMap<>();
        Map<String, String> keyWordAndMeshMap = new ConcurrentHashMap<>();
        Map<String, String> originalAndMeshMap = new ConcurrentHashMap<>();
        Collection<Integer> tableList = new ArrayList<>();
        NumberUtil.appendRange(0, tableMax + onePageNum, onePageNum, tableList);
        AtomicInteger sumNum = new AtomicInteger(0);
        int showNum = Math.max((tableList.size() / allTotal), 1) + (tableList.size() % allTotal);
        AtomicInteger startNum = new AtomicInteger(1);
        List<String> originalWordList = rSet.getWordList(); // 共1673个，先长的后短的，区分大小写。
        Set<String> smallWordSet = rSet.getSimpWordSet();
        Set<String> backgroundSet = rSet.toLowerCaseSet(rSet.getBackgroundSet());
        Set<String> discussionSet = rSet.toLowerCaseSet(rSet.getDiscussionSet());
        Set<String> methodsSet = rSet.toLowerCaseSet(rSet.getMethodsSet());
        Set<String> resultsSet = rSet.toLowerCaseSet(rSet.getResultsSet());
        Set<String> chinaSet = cList.chinaSet();
        Set<String> citySet = new HashSet<>();
        Set<String> subjectSet = cList.getSubjectSet();
        Set<String> provCitySet = Dept.getProvCitySet(htLocationChinaTable, "id", "prov`,`name", citySet);
        readMesh(meshTable, "id", "meshName`,`entryTerms", md5Map, keyWordAndMeshMap, originalAndMeshMap, reKwMap, lowerToUpperMap);
        Map<String, String> standardMap = new ConcurrentHashMap<>();
        standardMap.put("department medical", "Department Medical");
        Map<String, Set<String>> deptAndInfoSetMap = new ConcurrentHashMap<>();
        Map<String, Integer> deptAndSumMap = new ConcurrentHashMap<>();
        Map<String, Set<String>> unitAndInfoSetMap = new ConcurrentHashMap<>();
        Map<String, Integer> unitAndSumMap = new ConcurrentHashMap<>();
        Map<String, String> countryMap = cList.getCountryMap();
        Set<String> acadSet = uSet.getAcadSet();
        Set<String> centerSet = uSet.getCenterSet();
        Set<String> collSet = uSet.getCollSet();
        Set<String> hospSet = uSet.getHospSet();
        Set<String> univSet = uSet.getUnivSet();
        Set<String> removeSet = uSet.getRemoveSet();
        Set<String> dellSet = uSet.getDellSet();
        Set<String> toDeptSet = uSet.toDept();
        Map<String, En.FuDanStemEdu> fuDanStemEduMap = Guidelines.getFuDanStemEduMap(basicHospUnivDeptTable, "id", "info`,`fudan`,`stem`,`edu");
        tableList.parallelStream().forEach(startId -> {
                    try {
                        List<Entity> tableData = Db.use().query("select `" + autoId + "`,`" + field + "` from " + summaryTable + " where " + autoId + " between " + startId + " and " + (startId + onePageNum - 1) + limit);
                        if (print && sumNum.getAndIncrement() % showNum == 0) {
                            System.out.println("\033[31;1m" + format.format(new Date()) + " ⑼ 更新作者，现在开始：" + startNum.getAndIncrement() + " 总次数：" + allTotal + " 表名：" + summaryTable + Utils.printMemoryStr() + "\033[0m");
                        }
                        for (Entity entry : tableData) {
                            Integer id = entry.getInt(autoId);
                            if (null == id || id < 1) {
                                continue;
                            }
                            Set<List<String>> abbSet = new HashSet<>();
                            String title = Utils.getStrField(entry, "title");
                            String keyword = Utils.getStrField(entry, "keyword");
                            String ab = Utils.getStrField(entry, "ab");
                            String cnKw = Utils.getStrField(entry, "cn_kw");
                            String cnAb = Utils.getStrField(entry, "cn_ab");
                            int aid = Utils.getIntField(entry, "aid");
                            int pmId = Utils.getIntField(entry, "pmId");
                            String ut = Utils.getStrField(entry, "ut");
                            List<String> keywordList = (keyword.contains("\"]")) ? JSONObject.parseArray(keyword, String.class) : new ArrayList<>();
                            addKeySet(CollUtil.join(keywordList, "; "), new HashSet<>(), keyWordSumMap, cnKeyWordSumMap, lowerToUpperMap);
                            getAbbrKeyWord(title, fullAbbrKeyWordTable, aid, abbSet, pmId, ut, keyWordSumMap, lowerToUpperMap);
                            List<En.Trans> transList = transAbstract(ab, originalWordList, smallWordSet);
                            En.AbKeyMethod abKeyMethod = getTransStr(backgroundSet, discussionSet, methodsSet, resultsSet, transList); // 这里面是四个
                            // replaceAll(":([ ]{0,1})([0-9.]+)", "：$1$2")，英文的冒号，变成中文的冒号，比如：value: 123.45 -> value：123.45
                            ab = ab.replace("：", ": ").replace("。", ". ").replaceAll(" +", " ").trim().replaceAll(":([ ]{0,1})([0-9.]+)", "：$1$2");
                            getAbbrKeyWord(ab, fullAbbrKeyWordTable, aid, abbSet, pmId, ut, keyWordSumMap, lowerToUpperMap);

                            List<String> cnKeyList = (cnKw.contains("\"]")) ? JSONObject.parseArray(cnKw, String.class) : new ArrayList<>();
                            addKeySet(CollUtil.join(cnKeyList, "; "), new HashSet<>(), keyWordSumMap, cnKeyWordSumMap, lowerToUpperMap);
                            getCnAbbrKeyWord(cnAb, fullAbbrKeyWordTable, aid, abbSet, pmId, ut, keyWordSumMap, lowerToUpperMap);
                            En.AbKeyMethod cnAbKeyMethod = getCnAbKeyMethod(cnAb);

                            String c1Au = Utils.getStrField(entry, "c1_au");
                            String rpAu = Utils.getStrField(entry, "rp_au");
                            List<En.C1Auth> c1List = (c1Au.contains("sort")) ? JSONObject.parseArray(c1Au, En.C1Auth.class) : new ArrayList<>();
                            List<En.C1Auth> rpChangeC1 = (rpAu.contains("sort")) ? JSONObject.parseArray(rpAu, En.C1Auth.class) : new ArrayList<>();
                            Guidelines.setC1List(c1List, standardMap, acadSet, centerSet, collSet, hospSet, univSet, removeSet, countryMap, deptAndSumMap, dellSet,
                                    chinaSet, provCitySet, citySet, subjectSet, deptAndInfoSetMap, unitAndInfoSetMap, unitAndSumMap, toDeptSet, fuDanStemEduMap);
                            Guidelines.setC1List(rpChangeC1, standardMap, acadSet, centerSet, collSet, hospSet, univSet, removeSet, countryMap, deptAndSumMap, dellSet,
                                    chinaSet, provCitySet, citySet, subjectSet, deptAndInfoSetMap, unitAndInfoSetMap, unitAndSumMap, toDeptSet, fuDanStemEduMap);
                            String language = Utils.getStrField(entry, "language");
                            String[] split = language.replace(";-;", "ĺļ").replace("; ", "ĺļ").replace(";", "ĺļ").split("ĺļ");
                            List<String> languageList = new ArrayList<>();
                            for (String s : split) {
                                if (s.trim().length() > 2) {
                                    languageList.add(languageMap.getOrDefault(s.toLowerCase().trim(), s.trim()));
                                }
                            }
                            try {
                                Db.use().update(
                                        Entity.create().set("discussion", abKeyMethod.getDiscussion())
                                                .set("language", CollUtil.join(languageList, "; "))
                                                .set("results", abKeyMethod.getResults())
                                                .set("methods", abKeyMethod.getMethods())
                                                .set("background", abKeyMethod.getBackground())
                                                .set("abb_word", JSONUtil.toJsonStr(abbSet))
                                                .set("c1_au", JSONUtil.toJsonStr(c1List))
                                                .set("rp_au", JSONUtil.toJsonStr(rpChangeC1))
                                                .set("cn_discussion", cnAbKeyMethod.getDiscussion())
                                                .set("cn_results", cnAbKeyMethod.getResults())
                                                .set("cn_methods", cnAbKeyMethod.getMethods())
                                                .set("cn_background", cnAbKeyMethod.getBackground()),
                                        Entity.create(summaryTable).set(autoId, id));
                            } catch (SQLException e) {
                                e.printStackTrace();
                            }
                        }
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
        );

        // 将科室信息，包含医院、大学的统一给保存进basicHospUnivDeptTable表，可以在这一张表中处理复旦、STEM和教育部学科。此不需要每次都弄，除非要换大模型，否则可以一直使用已经规范的。
//        Set<String> set = new HashSet<>();
//        for (Map.Entry<String, Integer> entry : deptAndSumMap.entrySet()) {
//            if (entry.getKey().startsWith("[univ]") || entry.getKey().startsWith("[hosp]")) {
//                String dept = entry.getKey().substring("[univ]".length());
//                if (dept.length() > 1) {
//                    set.add(dept);
//                }
//            }
//        }
//        Db.use().execute("truncate table " + basicHospUnivDeptTable);
//        set.parallelStream().forEach(dept -> {
//            Set<String> univAdnInfoSet = (deptAndInfoSetMap.containsKey("[univ]" + dept)) ? deptAndInfoSetMap.get("[univ]" + dept) : new HashSet<>();
//            Set<String> hospAdnInfoSet = (deptAndInfoSetMap.containsKey("[hosp]" + dept)) ? deptAndInfoSetMap.get("[hosp]" + dept) : new HashSet<>();
//            int univSum = deptAndSumMap.getOrDefault("[univ]" + dept, 0);
//            int hospSum = deptAndSumMap.getOrDefault("[hosp]" + dept, 0);
//            try {
//                Db.use().insert(Entity.create(basicHospUnivDeptTable).set("info", dept).set("univ_c1s", JSONUtil.toJsonStr(univAdnInfoSet)).set("hosp_c1s", JSONUtil.toJsonStr(hospAdnInfoSet))
//                        .set("univ_sum", univSum).set("hosp_sum", hospSum).set("is_univ", univSum > 0 ? 1 : 0).set("is_hosp", hospSum > 0 ? 1 : 0));
//            } catch (SQLException e) {
//                e.printStackTrace();
//            }
//        });
//
//        // 需要规范的单位的信息
//        unitAndInfoSetMap.keySet().parallelStream().forEach(unit -> {
//            try {
//                Db.use().insert(Entity.create(hospUnivInfoSumTable).set("unit", unit).set("example", JSONUtil.toJsonStr(unitAndInfoSetMap.get(unit))).set("sum", unitAndSumMap.getOrDefault(unit, 0)));
//            } catch (SQLException e) {
//                e.printStackTrace();
//            }
//        });

        languageMap.clear();
        originalWordList.clear();
        smallWordSet.clear();
        backgroundSet.clear();
        discussionSet.clear();
        methodsSet.clear();
        resultsSet.clear();
        chinaSet.clear();
        citySet.clear();
        subjectSet.clear();
        provCitySet.clear();
        standardMap.clear();
        deptAndInfoSetMap.clear();
        deptAndSumMap.clear();
        unitAndInfoSetMap.clear();
        unitAndSumMap.clear();
        countryMap.clear();
        acadSet.clear();
        centerSet.clear();
        collSet.clear();
        hospSet.clear();
        univSet.clear();
        removeSet.clear();
        dellSet.clear();
        toDeptSet.clear();
        fuDanStemEduMap.clear();
        Set<String> originalSet = insertKeyWordSumMap(keyWordSumTable, keyWordSumMap, cnKeyWordSumMap, lowerToUpperMap, originalAndMeshMap);
        keyWordSumMap.clear();
        keyWordAndMeshMap.clear();
        md5Map.clear();
        cnKeyWordSumMap.clear();
        reKwMap.clear();
        Map<Integer, byte[]> aidAndKeyWordMap = updateKeyWord(summaryTable, limit, "id", "keyword`,`title`,`abb_word`,`ab`,`cn_kw`,`cn_ti`,`cn_ab`,`aid", true, 10, originalAndMeshMap, originalSet, lowerToUpperMap, termAndSumMap);
        originalAndMeshMap.clear();
        originalSet.clear();
        lowerToUpperMap.clear();
        return aidAndKeyWordMap;
    }

    public static Map<Integer, byte[]> updateKeyWord(String summaryTable, String limit, String autoId, String field, boolean print, int allTotal, Map<String, String> originalAndMeshMap, Set<String> originalSet, Map<String, String> lowerToUpperMap, Map<String, Integer> termAndSumMap) throws SQLException {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        int tableMax = Db.use().queryNumber("select max(" + autoId + ") from " + summaryTable).intValue();
        Map<Integer, byte[]> aidAndKeyWordMap = new ConcurrentHashMap<>();
        if (tableMax < 1) {
            return aidAndKeyWordMap;
        }
        int onePageNum = 10000;
        Collection<Integer> tableList = new ArrayList<>();
        NumberUtil.appendRange(0, tableMax + onePageNum, onePageNum, tableList);
        AtomicInteger sumNum = new AtomicInteger(0);
        int showNum = Math.max((tableList.size() / allTotal), 1) + (tableList.size() % allTotal);
        AtomicInteger startNum = new AtomicInteger(1);
        tableList.parallelStream().forEach(startId -> {
            try {
                List<Entity> tableData = Db.use().query("select `" + autoId + "`,`" + field + "` from " + summaryTable + " where " + autoId + " between " + startId + " and " + (startId + onePageNum - 1) + limit);
                if (print && sumNum.getAndIncrement() % showNum == 0) {
                    System.out.println("\033[31;1m" + format.format(new Date()) + " ⑽ 更新关键词，现在开始：" + startNum.getAndIncrement() + " 总次数：" + allTotal + " 表名：" + summaryTable + Utils.printMemoryStr() + "\033[0m");
                }
                // 更新关键词部分，2025.03.16日，采用tableList使用parallelStream，tableData使用executor的方法，进行实验。
                try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
                    for (Entity entry : tableData) {
                        executor.submit(() -> {
                            Integer id = entry.getInt(autoId);
                            int aid = Utils.getIntField(entry, "aid");
                            if (null != id && id > 0 && aid > 0) {
                                String keywords = entry.getStr("keyword");
                                List<List<String>> kwListList = new ArrayList<>();
                                if (null != keywords && keywords.contains("\"]")) {
                                    List<String> kwList = JSONObject.parseArray(keywords, String.class);
                                    addListList(originalAndMeshMap, kwList, kwListList, lowerToUpperMap);
                                }

                                String title = entry.getStr("title");
                                List<List<String>> tiListList = new ArrayList<>();
                                List<String> tiKeyList = (null != title && title.length() > 4) ? getTiKey(title, originalSet, lowerToUpperMap) : new ArrayList<>();
                                addListList(originalAndMeshMap, tiKeyList, tiListList, lowerToUpperMap);

                                List<List<String>> abbListList = new ArrayList<>();
                                String abbWord = entry.getStr("abb_word");
                                List<String> abbWordList = (null != abbWord && abbWord.contains("\"]")) ? JSONObject.parseArray(abbWord, String.class) : new ArrayList<>();
                                for (String lists : abbWordList) {
                                    List<String> list = JSONObject.parseArray(lists, String.class);
                                    if (list.size() == 2) {
                                        String lower = list.getFirst();
                                        List<String> result = new ArrayList<>();
                                        result.add(lowerToUpperMap.getOrDefault(lower, lower));
                                        result.add(list.get(1));
                                        abbListList.add(result);
                                    }
                                }

                                List<List<String>> abListList = new ArrayList<>();
                                String ab = entry.getStr("ab");
                                List<String> abKeyList = (null != ab && ab.length() > 10) ? getAbKey(ab, originalSet, lowerToUpperMap) : new ArrayList<>();
                                addListList(originalAndMeshMap, abKeyList, abListList, lowerToUpperMap);

                                // 关键词基本上都是保持原有的样子不作改变，而从标题和摘要的，则要使用提取的程序。
                                String cnKeywords = entry.getStr("cn_kw");
                                if (null != cnKeywords && cnKeywords.contains("\"]")) {
                                    List<String> kwList = JSONObject.parseArray(cnKeywords, String.class);
                                    listToListList(kwList, kwListList, lowerToUpperMap);
                                }

                                String cnTi = entry.getStr("cn_ti");
                                List<String> cnTiKeyList = (null != cnTi && cnTi.length() > 4) ? getCnTiKey(cnTi, originalSet, lowerToUpperMap) : new ArrayList<>();
                                listToListList(cnTiKeyList, tiListList, lowerToUpperMap);

                                String cnAb = entry.getStr("cn_ab");
                                List<String> cnAbKeyList = (null != cnAb && cnAb.length() > 10) ? getCnAbKey(cnAb, originalSet, lowerToUpperMap) : new ArrayList<>();
                                listToListList(cnAbKeyList, abListList, lowerToUpperMap);

                                if (!tiListList.isEmpty() || !kwListList.isEmpty() || !abListList.isEmpty() || !abbListList.isEmpty()) {
                                    Set<String> allSet = new HashSet<>();
                                    addAllSet(tiListList, allSet);
                                    addAllSet(kwListList, allSet);
                                    addAllSet(abListList, allSet);
                                    addAllSet(abbListList, allSet);
                                    for (String term : allSet) {
                                        termAndSumMap.compute(term, (key, value) -> value != null ? (value + 1) : 1);
                                    }
                                    En.KeyWords keyWords = new En.KeyWords();
                                    keyWords.setTiListList(tiListList);
                                    keyWords.setKwListList(kwListList);
                                    keyWords.setAbListList(abListList);
                                    keyWords.setAbbListList(abbListList);
                                    byte[] zip = ZipUtil.gzip(JSONUtil.toJsonStr(keyWords), "utf-8");
                                    aidAndKeyWordMap.put(aid, zip);
                                }
                            }
                        });
                    }
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        });
        return aidAndKeyWordMap;
    }

    public static List<String> getCnAbKey(String abs, Set<String> originalSet, Map<String, String> lowerToUpperMap) {
        List<String> abList = new ArrayList<>();
        if (null != abs && abs.length() > 1 && (Pattern.compile("[a-z]").matcher(abs).find() || Utils.isContainChinese(abs))) {
            // 英文的点后面，如果是中文或字母给替换为空格，如果是数字则仍保留这个点，意思是0.05是不能切割的。
            String ab = abs.replace("；", " ").replace("，", ",").replace("。", " ").replace("、", " ").replace("（", " ").replace("）", " ").replace("【", " ")
                    .replace("：", " ").replace("！", " ").replace("？", " ").replace(";", " ").replaceAll("\\.([\u4e00-\u9fa5a-zA-Z])", " $1").replace("(", " ").replace("】", " ")
                    .replace(")", " ").replace(":", " ").replace("!", " ").replace("?", " ").replace(" ,", ",").replace(", ", ",").replaceAll(" +", " ").trim();
            String[] split = ab.split(" ");
            for (String s : split) {
                if (s.trim().length() > 4) {
                    extract(s, originalSet, abList, lowerToUpperMap);
                }
            }
        }
        return abList;
    }

    public static void extract(String freeWords, Set<String> originalSet, List<String> extractList, Map<String, String> lowerToUpperMap) {
        // 2025.04.19，如果所有的英文的单词，不含有小写的字母，则不认为是大写的词组，即必须含有小写字母，才可以认定是词组。
        if (null != freeWords && freeWords.length() > 1 && (Pattern.compile("[a-z]").matcher(freeWords).find() || Utils.isContainChinese(freeWords))) {
            String spl = Utils.isContainChinese(freeWords) ? "" : " ";
            int splLen = spl.length();
            List<String> list = Arrays.asList(freeWords.split(spl));
            int iLen = 0;
            int listLen = list.size();
            for (int i = 0; i < listLen; i++) {
                if (freeWords.length() >= iLen) {
                    String iStr = freeWords.substring(iLen);
                    iLen += list.get(i).length() + splLen;
                    int jLen = 0;
                    for (int j = listLen - 1; j >= 0; j--) {
                        if (iStr.length() - jLen >= 0) {
                            String jStr = iStr.substring(0, iStr.length() - jLen);
                            jLen += list.get(j).length() + splLen;
                            if (originalSet.contains(jStr) || originalSet.contains(jStr.toLowerCase())) {
                                boolean a = Utils.isContainChinese(jStr);
                                boolean b = isKeyWordFull(jStr);
                                boolean c = isKeyWordAbbr(jStr);
                                if (a || b || c) {
                                    // 2025.04.20 原来中文的关键词是没有加入lowerToUpperMap的，今天已经给加入了，所以可以使用下面的方法。为什么要加入？因为中文的关键词，也有可以内含有英文的字符串，而这个英文的字符串不一定是大小写的。
                                    if ((a || b) && lowerToUpperMap.containsKey(jStr)) {
                                        extractList.add(lowerToUpperMap.get(jStr));
                                    } else if (c) {
                                        extractList.add(jStr);
                                    }
                                    iLen += (jStr.length() - list.get(i).length());
                                    i = i + jStr.split(spl).length - 1;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    // 从摘要中提取关键词，先以句号切割，再一句句提取。
    public static List<String> getAbKey(String abs, Set<String> originalSet, Map<String, String> lowerToUpperMap) {
        List<String> abList = new ArrayList<>();
        // 2025.04.19，如果所有的英文的单词，不含有小写的字母，则不认为是大写的词组，即必须含有小写字母，才可以认定是词组。
        if (null != abs && abs.length() > 11 && abs.contains(". ") && (Pattern.compile("[a-z]").matcher(abs).find() || Utils.isContainChinese(abs))) {
            String ab = abs.replace("? ", ". ").replace("; ", ". ").replace(": ", ". ").trim();
            ab = ab.replace("<p>", " ").replace("</p>", " ").replace("  ", " ").trim();
            ab = ab.endsWith(".") ? ab.substring(0, ab.length() - 1).trim() : ab.trim();
            String[] split = ab.split("\\. ");
            for (String s : split) {
                s = s.trim();
                s = s.endsWith(".") ? s.substring(0, s.length() - 1) : s;
                if (s.length() > 4 && s.contains(" ")) {
                    extract(s, originalSet, abList, lowerToUpperMap);
                }
            }
        }
        return abList;
    }

    // 从标题中提取关键词，部分标题中存在方括号，先给予去除。
    public static List<String> getCnTiKey(String ti, Set<String> originalSet, Map<String, String> lowerToUpperMap) {
        List<String> titleList = new ArrayList<>();
        if (null != ti && ti.length() > 4 && (Pattern.compile("[a-z]").matcher(ti).find() || Utils.isContainChinese(ti))) {
            extract(ti, originalSet, titleList, lowerToUpperMap);
        }
        return titleList;
    }

    // 从标题中提取关键词，部分标题中存在方括号，先给予去除。
    public static List<String> getTiKey(String ti, Set<String> originalSet, Map<String, String> lowerToUpperMap) {
        List<String> titleSet = new ArrayList<>();
        // 2025.04.19，如果所有的英文的单词，不含有小写的字母，则不认为是大写的词组，即必须含有小写字母，才可以认定是词组。
        if (null != ti && ti.length() > 4 && ti.contains(" ") && (Pattern.compile("[a-z]").matcher(ti).find() || Utils.isContainChinese(ti))) {
            ti = ti.trim().startsWith("[") ? ti.trim().substring(1).trim() : ti.trim();
            ti = ti.endsWith(".") ? ti.substring(0, ti.length() - 1).trim() : ti.trim();
            ti = ti.endsWith("]") ? ti.substring(0, ti.length() - 1).trim() : ti.trim();
            ti = ti.endsWith(".") ? ti.substring(0, ti.length() - 1).trim() : ti.trim();
            extract(ti, originalSet, titleSet, lowerToUpperMap);
        }
        return titleSet;
    }

    public static void listToListList(List<String> cnAbKeyList, List<List<String>> abListList, Map<String, String> lowerToUpperMap) {
        for (String s : cnAbKeyList) {
            if (s.length() > 1) {
                List<String> list = new ArrayList<>();
                String lower = s.toLowerCase();
                list.add(lowerToUpperMap.getOrDefault(lower, lower));
                abListList.add(list);
            }
        }
    }

    public static void addListList(Map<String, String> originalAndMeshMap, List<String> keywordList, List<List<String>> kwListList, Map<String, String> lowerToUpperMap) {
        for (String key : keywordList) {
            List<String> list = new ArrayList<>();
            key = key.replaceAll(" +", " ").trim();
            key = key.endsWith(".") ? key.substring(0, key.length() - 1).trim() : key;
            String lower = key.toLowerCase();
            if (lower.length() > 1) {
                list.add(lowerToUpperMap.getOrDefault(lower, lower));
                // 为什么分两次？原因是originalAndMeshMap的键也是两部分的，一部分是符合全写的，全写的全部为小写。一部分是符合简写的，简写的区分大小写，所以不能全部都给转成小写来判断。
                if (originalAndMeshMap.containsKey(key)) {
                    String mesh = originalAndMeshMap.get(key);
                    mesh = lowerToUpperMap.getOrDefault(mesh, mesh);
                    list.add(mesh);
                    list.add("m");
                } else if (originalAndMeshMap.containsKey(lower)) {
                    String mesh = originalAndMeshMap.get(lower);
                    mesh = lowerToUpperMap.getOrDefault(mesh, mesh);
                    list.add(mesh);
                    list.add("m");
                }
                kwListList.add(list);
            }
        }
    }

    public static void addAllSet(List<List<String>> tiListList, Set<String> allSet) {
        for (List<String> list : tiListList) {
            for (String s : list) {
                // 当s.length() > 1时，自动把"m".equals(s)给过滤掉了。
                if (s.length() > 1) {
                    allSet.add(s);
                }
            }
        }
    }

    public static En.AbKeyMethod getCnAbKeyMethod(String cnAb) {
        En.AbKeyMethod abKeyMethod = new En.AbKeyMethod();
        if (null != cnAb && cnAb.length() > 5) {
            cnAb = "。" + cnAb.replaceAll("(\\.|\\. |。|。 |^|\n)背景和目的", "。Ł1ł").replaceAll("(\\.|\\. |。|。 |^|\n)材料与方法", "。Ł2ł")
                    .replaceAll("(\\.|\\. |。|。 |^|\n)(结果|目的|背景|结论|方法|讨论)([ :\n])", "。ĺļ$2:ҁҘ");
            cnAb = (!cnAb.contains("Ł1ł")) ? cnAb.replaceFirst("背景和目的", "ĺļ背景和目的ҁҘ") : cnAb.replace("Ł1ł", "ĺļ背景和目的ҁҘ");
            cnAb = (!cnAb.contains("Ł2ł")) ? cnAb.replaceFirst("材料与方法", "ĺļ材料与方法ҁҘ") : cnAb.replace("Ł2ł", "ĺļ材料与方法ҁҘ");
            cnAb = (!cnAb.contains("结果:ҁҘ")) ? cnAb.replaceFirst("([\\u4e00-\\u9fa5])(\\.|\\. |。|。 )(结果)", "$1。ĺļ$3:ҁҘ") : cnAb;
            cnAb = (!cnAb.contains("目的:ҁҘ")) ? cnAb.replaceFirst("([\\u4e00-\\u9fa5])(\\.|\\. |。|。 )(目的)", "$1。ĺļ$3:ҁҘ") : cnAb;
            cnAb = (!cnAb.contains("背景:ҁҘ")) ? cnAb.replaceFirst("([\\u4e00-\\u9fa5])(\\.|\\. |。|。 )(背景)", "$1。ĺļ$3:ҁҘ") : cnAb;
            cnAb = (!cnAb.contains("结论:ҁҘ")) ? cnAb.replaceFirst("([\\u4e00-\\u9fa5])(\\.|\\. |。|。 )(结论)", "$1。ĺļ$3:ҁҘ") : cnAb;
            cnAb = (!cnAb.contains("方法:ҁҘ")) ? cnAb.replaceFirst("([\\u4e00-\\u9fa5])(\\.|\\. |。|。 )(方法)", "$1。ĺļ$3:ҁҘ") : cnAb;
            cnAb = (!cnAb.contains("讨论:ҁҘ")) ? cnAb.replaceFirst("([\\u4e00-\\u9fa5])(\\.|\\. |。|。 )(讨论)", "$1。ĺļ$3:ҁҘ") : cnAb;
            cnAb = (!cnAb.contains("结果:ҁҘ")) ? cnAb.replaceFirst("(\\.|\\. |。|。 |^|\n)(结果)", "。ĺļ$2:ҁҘ") : cnAb;
            cnAb = (!cnAb.contains("目的:ҁҘ")) ? cnAb.replaceFirst("(\\.|\\. |。|。 |^|\n)(目的)", "。ĺļ$2:ҁҘ") : cnAb;
            cnAb = (!cnAb.contains("背景:ҁҘ")) ? cnAb.replaceFirst("(\\.|\\. |。|。 |^|\n)(背景)", "。ĺļ$2:ҁҘ") : cnAb;
            cnAb = (!cnAb.contains("结论:ҁҘ")) ? cnAb.replaceFirst("(\\.|\\. |。|。 |^|\n)(结论)", "。ĺļ$2:ҁҘ") : cnAb;
            cnAb = (!cnAb.contains("方法:ҁҘ")) ? cnAb.replaceFirst("(\\.|\\. |。|。 |^|\n)(方法)", "。ĺļ$2:ҁҘ") : cnAb;
            cnAb = (!cnAb.contains("讨论:ҁҘ")) ? cnAb.replaceFirst("(\\.|\\. |。|。 |^|\n)(讨论)", "。ĺļ$2:ҁҘ") : cnAb;
            cnAb = cnAb.startsWith("。") ? cnAb.substring("。".length()).trim() : cnAb.trim();
            String[] split = cnAb.split("ĺļ");
            List<En.Trans> backgroundList = new ArrayList<>();
            List<En.Trans> discussionList = new ArrayList<>();
            List<En.Trans> methodsList = new ArrayList<>();
            List<En.Trans> resultsList = new ArrayList<>();
            for (String s : split) {
                s = s.endsWith("\n。") ? s.substring(0, s.length() - "\n。".length()) : s;
                s = s.replace("\n", " ").replace("　", " ").replaceAll(" +", " ").trim();
                if (!"。".equals(s) && s.contains("ҁҘ")) {
                    cnAddTrans("背景和目的:ҁҘ", s, "background and purpose", backgroundList);
                    cnAddTrans("材料与方法:ҁҘ", s, "materials and methods", methodsList);
                    cnAddTrans("结果:ҁҘ", s, "results", resultsList);
                    cnAddTrans("目的:ҁҘ", s, "purpose", backgroundList);
                    cnAddTrans("背景:ҁҘ", s, "background", backgroundList);
                    cnAddTrans("结论:ҁҘ", s, "conclusion", resultsList);
                    cnAddTrans("方法:ҁҘ", s, "methods", methodsList);
                    cnAddTrans("讨论:ҁҘ", s, "discussion", discussionList);
                }
            }
            // 一般来说，不允许只出现一项或二项，而至少应该有三项才是完整的，所以当不足3项时，直接返回空的，只有满足时才返回。
            if ((backgroundList.size() + discussionList.size() + methodsList.size() + resultsList.size()) > 2) {
                abKeyMethod.setBackground(JSONUtil.toJsonStr(backgroundList));
                abKeyMethod.setDiscussion(JSONUtil.toJsonStr(discussionList));
                abKeyMethod.setMethods(JSONUtil.toJsonStr(methodsList));
                abKeyMethod.setResults(JSONUtil.toJsonStr(resultsList));
            }
        }
        return abKeyMethod;
    }

    public static void cnAddTrans(String cnName, String info, String label, List<En.Trans> list) {
        if (info.contains(cnName)) {
            En.Trans trans = new En.Trans();
            trans.setCn(info.replace("ҁҘ", "").replace(":：", ": "));
            trans.setEn("");
            trans.setLabel(label);
            list.add(trans);
        }
    }

    public static void getCnAbbrKeyWord(String cnAb, String fullAbbrKeyWordTable, Integer aid, Set<List<String>> abbSet, Integer pmId, String ut, Map<List<String>, Integer> keyWordSumMap, Map<String, String> lowerToUpperMap) {
        // 处理中文摘要
        if (null != cnAb && cnAb.length() > 10) {
            cnAb = cnAb.replace("，", ", ").replaceAll(" +", " ").replace("（", "(").replace("）", ")").replace("( ", "(").replace(" )", ")").trim();
            if (cnAb.contains("(") && cnAb.contains(")") && Pattern.compile("[a-z]").matcher(cnAb).find()) {
                Matcher matcher = Pattern.compile("\\(([^()]+)\\)").matcher(cnAb);
                if (matcher.find()) {
                    while (matcher.find()) {
                        String s = matcher.group(1);
                        String[] split = s.split(",");
                        if (split.length == 2 && s.matches(".*[a-z].*[A-Z].*") && s.contains(",") && s.contains(" ") && !s.contains("=") && !s.matches(".*[\u4e00-\u9fa5].*")) {
                            String full = split[0];
                            String abbr = split[1];
                            String equals = fullAndAbbrEquals(full, abbr);
                            if (!"false".equals(equals)) {
                                full = ("true".equals(equals)) ? full : equals;
                                full = (full.startsWith("(") || full.startsWith(")")) ? full.substring(1) : full;
                                full = full.startsWith("\"") && full.endsWith("\"") ? full.substring(1, full.length() - 1) : full;
                                full = full.startsWith("'") && full.endsWith("'") ? full.substring(1, full.length() - 1) : full;
//                                System.out.println("匹配成功 full:" + full + " abbr:" + abbr);
                                insert(fullAbbrKeyWordTable, s, full, abbr, 1, aid, pmId, ut);
                                if (isKeyWordFull(full) && isKeyWordAbbr(abbr)) {
                                    full = full.endsWith(".") ? full.substring(0, full.length() - 1).trim() : full.trim();
                                    abbr = abbr.endsWith(".") ? abbr.substring(0, abbr.length() - 1).trim() : abbr.trim();
                                    List<String> fullAbbrList = new ArrayList<>();
                                    String lower = full.toLowerCase();
                                    lowerToUpperMap.putIfAbsent(lower, full);
                                    fullAbbrList.add(lower);
                                    fullAbbrList.add(abbr);
                                    abbSet.add(fullAbbrList);
                                    // 2025.04.20 下面三行给予取消，没有意义。
//                                    List<String> fullList = new ArrayList<>();
//                                    fullList.add(lower);
//                                    keyWordSumMap.compute(fullList, (key, value) -> value != null ? (value + 1) : 1);
                                    keyWordSumMap.compute(fullAbbrList, (key, value) -> value != null ? (value + 1) : 1);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    public static void addAbbSet(String paragraph, List<List<String>> abbList, Map<String, String> lowerToUpperMap) {
        if (null != paragraph && paragraph.contains("(") && paragraph.contains(")")) {
            String[] paragraphSplit = paragraph.replace(".</p>", ". </p>").replace("<p>", " <p> ").replace("</strong>", " </strong> ").replace("<strong class", " <strong class")
                    .replace("/", " / ").replace("[", " [ ").replace("]", " ] ").split("\\. ");
            for (String str : paragraphSplit) {
                String[] sentenceSplit = str.split("\\)");
                for (String sentence : sentenceSplit) {
                    sentence += ")";
                    if (sentence.contains("(") && sentence.contains(")")) {
                        sentence = sentence.replace("(", " (").replace(")", ") ").replaceAll(" +", " ").replace(" -", "-")
                                .replace("- ", "-").replace("( ", "(").replace(" )", ")").trim();
                        String[] list = sentence.split(" ");
                        int end = 0;
                        for (int i = 0; i < list.length; i++) {
                            String s = list[i];
                            if (s.startsWith("(") && s.endsWith(")") && s.length() > 3) {
                                String abbr = s.substring(1, s.length() - 1);
                                if (isEnglish(abbr)) {
                                    int start = 0;
                                    for (int j = 0; j < i; j++) {
                                        if (end > start && sentence.length() > end) {
                                            String full = sentence.substring(start, end).trim();
                                            start += list[j].length() + 1;
                                            String equals = fullAndAbbrEquals(full, abbr);
                                            if (!"false".equals(equals)) {
                                                full = ("true".equals(equals)) ? full : equals;
                                                full = (full.startsWith("(") || full.startsWith(")")) ? full.substring(1) : full;
                                                full = full.startsWith("\"") && full.endsWith("\"") ? full.substring(1, full.length() - 1) : full;
                                                full = full.startsWith("'") && full.endsWith("'") ? full.substring(1, full.length() - 1) : full;
                                                if (isKeyWordFull(full) && isKeyWordAbbr(abbr)) {
                                                    full = full.endsWith(".") ? full.substring(0, full.length() - 1).trim() : full.trim();
                                                    abbr = abbr.endsWith(".") ? abbr.substring(0, abbr.length() - 1).trim() : abbr.trim();
                                                    List<String> fullAbbrList = new ArrayList<>();
                                                    String lower = full.toLowerCase();
                                                    lowerToUpperMap.putIfAbsent(lower, full);
                                                    fullAbbrList.add(lower);
                                                    fullAbbrList.add(abbr);
                                                    abbList.add(fullAbbrList);
                                                }
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                            end += list[i].length() + 1;
                        }
                    }
                }
            }
        }
    }

    // 提取关键词，此类词的特征是前面是全写，后面有一个括号，比如：hepatocyte growth factor (HGF)->hepatocyte growth factor
    public static void getAbbrKeyWord(String paragraph, String fullAbbrKeyWordTable, Integer aid, Set<List<String>> abbSet, Integer pmId, String ut, Map<List<String>, Integer> keyWordSumMap, Map<String, String> lowerToUpperMap) {
         // 此程序处理 title 和ab 里面关键词 处理 这个意思是必须包含() 才处理么 部分标题没有() 就都忽略了? todo gcr
        if (null != paragraph && paragraph.contains("(") && paragraph.contains(")") && null != aid && aid > 0) {
            String[] paragraphSplit = paragraph.replace(".</p>", ". </p>").replace("<p>", " <p> ").replace("</strong>", " </strong> ").replace("<strong class", " <strong class")
                    .replace("/", " / ").replace("[", " [ ").replace("]", " ] ").split("\\. ");
            // 2025.04.19，如果所有的英文的单词，不含有小写的字母，则不认为是大写的词组，即必须含有小写字母，才可以认定是词组。
            if (Pattern.compile("[a-z]").matcher(paragraph).find()) {
                for (String str : paragraphSplit) {
                    String[] sentenceSplit = str.split("\\)");
                    for (String sentence : sentenceSplit) {
                        sentence += ")";
                        String original = sentence;
//                        System.out.println("original:" + original);
                        if (sentence.contains("(") && sentence.contains(")")) {
                            sentence = sentence.replace("(", " (").replace(")", ") ").replaceAll(" +", " ").replace(" -", "-")
                                    .replace("- ", "-").replace("( ", "(").replace(" )", ")").trim();
                            String[] list = sentence.split(" ");
                            int end = 0;
                            for (int i = 0; i < list.length; i++) {
                                String s = list[i];
                                if (s.startsWith("(") && s.endsWith(")") && s.length() > 3) {
                                    String abbr = s.substring(1, s.length() - 1);
                                    if (isEnglish(abbr)) {
                                        int start = 0;
                                        boolean bool = true;
                                        for (int j = 0; j < i; j++) {
                                            if (end > start && sentence.length() > end) {
                                                String full = sentence.substring(start, end).trim();
                                                start += list[j].length() + 1;
                                                String equals = fullAndAbbrEquals(full, abbr);
                                                if (!"false".equals(equals)) {
                                                    full = ("true".equals(equals)) ? full : equals;
                                                    bool = false;
                                                    full = (full.startsWith("(") || full.startsWith(")")) ? full.substring(1).trim() : full;
                                                    full = full.startsWith("\"") && full.endsWith("\"") ? full.substring(1, full.length() - 1).trim() : full;
                                                    full = full.startsWith("'") && full.endsWith("'") ? full.substring(1, full.length() - 1).trim() : full;
//                                                    System.out.println("匹配成功 full:" + full + " abbr:" + abbr);
                                                    insert(fullAbbrKeyWordTable, original, full, abbr, 1, aid, pmId, ut);
                                                    if (isKeyWordFull(full) && isKeyWordAbbr(abbr)) {
                                                        full = full.endsWith(".") ? full.substring(0, full.length() - 1) : full;
                                                        abbr = abbr.endsWith(".") ? abbr.substring(0, abbr.length() - 1) : abbr;
                                                        List<String> fullAbbrList = new ArrayList<>();
                                                        String lower = full.toLowerCase();
                                                        lowerToUpperMap.putIfAbsent(lower, full);
                                                        fullAbbrList.add(lower);
                                                        fullAbbrList.add(abbr);
                                                        abbSet.add(fullAbbrList);
                                                        // 2025.04.20 下面的三行没有意义，给予取消。
//                                                        List<String> fullList = new ArrayList<>();
//                                                        fullList.add(lower);
//                                                        keyWordSumMap.compute(fullList, (key, value) -> value != null ? (value + 1) : 1);
                                                        keyWordSumMap.compute(fullAbbrList, (key, value) -> value != null ? (value + 1) : 1);
                                                    }
                                                    break;
                                                }
                                            }
                                        }
                                        if (bool) {
                                            insert(fullAbbrKeyWordTable, original, "", abbr, 0, aid, pmId, ut);
                                        }
                                    }
                                }
                                end += list[i].length() + 1;
                            }
                        }
                    }
                }
            }
        }
    }


    // 判断词组与缩写的单词之间是否相等，如果词组内各单词的首字母与缩写的单词的各个首字母相等，或者首字母相等后半部分包含，可以认定为相等。比如：Mendelian randomization-Egger (MR-Egger)或hepatocyte growth factor (HGF)，要求全写的词组full必须包含空格，缩写的单词abbr必须大于1位，且首字母必须相同。
    public static String fullAndAbbrEquals(String full, String abbr) {
        if (null != full && null != abbr && (full.trim().contains(" ") || full.contains("-") || full.length() > 11) && abbr.length() > 1 && full.length() >= abbr.length()) {
            abbr = NumberUtil.isInteger(abbr.substring(0, 1)) ? abbr.substring(1) : abbr;
            // of 3-methoxy-O-desmethyl encainide (MODE)
            String[] fSplit = full.toLowerCase().split(" ");
            String[] aSplit = abbr.toLowerCase().split("");
            if (fSplit[0].contains("-" + aSplit[0]) && fSplit.length == 2 && aSplit.length == 4) {
                if (fSplit[0].contains("-" + aSplit[1]) && fSplit[0].contains("-" + aSplit[2]) && fSplit[1].startsWith(aSplit[3])) {
                    return full;
                }
            }
            // labelled with an anti-human leukocyte antigen (HLA)
            if (full.toLowerCase().contains("-" + abbr.substring(0, 1).toLowerCase())) {
                String[] fullSplit = full.toLowerCase().split(" ");
                String[] abbrSplit = abbr.toLowerCase().split("");
                int isOk = 0;
                String result = "";
                for (int i = 0; i < fullSplit.length; i++) {
                    int l = i + fullSplit.length - abbrSplit.length;
                    if (l > 0 && fullSplit.length > l && abbrSplit.length > i) {
                        String f = fullSplit[i + fullSplit.length - abbrSplit.length];
                        String a = abbrSplit[i];
                        if (f.contains("-" + a) || f.startsWith(a)) {
                            isOk++;
                            result += f + " ";
                        } else {
                            result = "";
                            isOk = 0;
                            break;
                        }
                    }
                }
                if (isOk >= abbrSplit.length) {
                    return result.trim();
                }
            }

            // Three-domain Interoceptive Sensations Questionnaire (THISQ)
            if (fSplit.length == 4 && aSplit.length == 5 && fSplit[0].contains("-")) {
                if (fSplit[0].startsWith(aSplit[0]) && fSplit[0].contains(aSplit[1]) && fSplit[1].startsWith(aSplit[2]) && fSplit[2].startsWith(aSplit[3]) && fSplit[3].startsWith(aSplit[4])) {
//                    System.out.println("3.2");
                    return "true";
                }
            }

            if (fSplit.length == aSplit.length) {
                boolean bool = true;
                for (int i = 0; i < fSplit.length; i++) {
                    if (!fSplit[i].substring(0, 1).equals(aSplit[i])) {
                        bool = false;
                        break;
                    }
                }
                if (bool) {
//                    System.out.println("2.1");
                    return "true";
                }
            }

            //In this study, reduced graphene oxide-TiO₂ (RGO-TiO₂)
            String[] fs = full.toLowerCase().split("-");
            String[] as = abbr.toLowerCase().split("-");
//            System.out.println("....~~~full:" + full + " abbr:" + abbr + " fs.length:" + fs.length + " as.length:" + as.length);
            if (fs.length == as.length && as.length == 2 && fs[1].equals(as[1]) && fs[1].length() > 1) {
                String[] f = fs[0].split(" ");
                String[] a = as[0].split("");
                if (f.length == a.length && f.length > 1) {
                    boolean bool = true;
                    for (int i = 0; i < f.length; i++) {
                        if (!f[i].substring(0, 1).equals(a[i])) {
                            bool = false;
                            break;
                        }
                    }
                    if (bool) {
//                        System.out.println("2.1");
                        return "true";
                    }
                }
            }

            if (fs.length == as.length && as.length == 3) {
                if (fs[0].startsWith(as[0]) && fs[1].startsWith(as[1])) {
//                    System.out.println("fs[2]:" + fs[2] + " as[2]:" + as[2]);
                    String[] f = fs[2].trim().split(" ");
                    String[] a = as[2].trim().split("");
                    if (f.length == a.length && f.length > 1) {
                        boolean bool = true;
                        for (int i = 0; i < f.length; i++) {
                            if (!f[i].substring(0, 1).equals(a[i])) {
                                bool = false;
                                break;
                            }
                        }
                        if (bool) {
//                            System.out.println("2.2");
                            return "true";
                        }
                    } else if (fs[2].trim().equals(as[2].trim())) {
//                        System.out.println("2.2");
                        return "true";
                    }
                }
            }

            full = full.startsWith("-") ? full.substring(1) : full;

            if (full.replace("(", "").replace("\"", "").replace("'", "").replace(")", "").substring(0, 1).equalsIgnoreCase(abbr.substring(0, 1))) {
                abbr = abbr.contains("α") && !full.contains("α") ? abbr.replace("α", " alpha ") : abbr;
                // Transforming growth factor-beta 1 (TGF-β1)
                abbr = abbr.contains("β") && !full.contains("β") ? abbr.replace("β", " beta ") : abbr;
                abbr = abbr.contains("γ") && !full.contains("γ") ? abbr.replace("γ", " gamma ") : abbr;
                abbr = abbr.contains("δ") && !full.contains("δ") ? abbr.replace("δ", " delta ") : abbr;
                abbr = abbr.contains("ε") && !full.contains("ε") ? abbr.replace("ε", " epsilon ") : abbr;
                abbr = abbr.contains("ζ") && !full.contains("ζ") ? abbr.replace("ζ", " zeta ") : abbr;
                abbr = abbr.contains("η") && !full.contains("η") ? abbr.replace("η", " eta ") : abbr;
                abbr = abbr.contains("θ") && !full.contains("θ") ? abbr.replace("θ", " theta ") : abbr;
                abbr = abbr.contains("ι") && !full.contains("ι") ? abbr.replace("ι", " iota ") : abbr;
                abbr = abbr.contains("κ") && !full.contains("κ") ? abbr.replace("κ", " kappa ") : abbr;
                abbr = abbr.contains("λ") && !full.contains("λ") ? abbr.replace("λ", " lambda ") : abbr;
                abbr = abbr.contains("μ") && !full.contains("μ") ? abbr.replace("μ", " mu ") : abbr;
                abbr = abbr.contains("ν") && !full.contains("ν") ? abbr.replace("ν", " nu ") : abbr;
                abbr = abbr.contains("ξ") && !full.contains("ξ") ? abbr.replace("ξ", " xi ") : abbr;
                abbr = abbr.contains("ο") && !full.contains("ο") ? abbr.replace("ο", " omicron ") : abbr;
                abbr = abbr.contains("π") && !full.contains("π") ? abbr.replace("π", " pi ") : abbr;
                abbr = abbr.contains("ρ") && !full.contains("ρ") ? abbr.replace("ρ", " rho ") : abbr;
                abbr = abbr.contains("σ") && !full.contains("σ") ? abbr.replace("σ", " sigma ") : abbr;
                abbr = abbr.contains("τ") && !full.contains("τ") ? abbr.replace("τ", " tau ") : abbr;
                abbr = abbr.contains("υ") && !full.contains("υ") ? abbr.replace("υ", " upsilon ") : abbr;
                abbr = abbr.contains("φ") && !full.contains("φ") ? abbr.replace("φ", " phi ") : abbr;
                abbr = abbr.contains("χ") && !full.contains("χ") ? abbr.replace("χ", " chi ") : abbr;
                abbr = abbr.contains("ψ") && !full.contains("ψ") ? abbr.replace("ψ", " psi ") : abbr;
                abbr = abbr.contains("ω") && !full.contains("ω") ? abbr.replace("ω", " omega ") : abbr;
                if (full.equalsIgnoreCase(abbr)) {
//                    System.out.println("1");
                    return "true";
                }

                // 'Miniature Inverted-repeat Transposable Element' -> MITE
                if (full.startsWith("'") && full.endsWith("'")) {
                    full = full.substring(1, full.length() - 1);
                    String[] fullSplit = full.split(" ");
                    String[] abbrSplit = abbr.split("");
                    if (fullSplit.length == abbrSplit.length) {
                        boolean bool = true;
                        for (int i = 0; i < fullSplit.length; i++) {
                            if (!fullSplit[i].substring(0, 1).equals(abbrSplit[i])) {
                                bool = false;
                                break;
                            }
                        }
                        if (bool) {
//                            System.out.println("2");
                            return "true";
                        }
                    }
                }

                // "Miniature Inverted-repeat Transposable Element" -> MITE
                if (full.startsWith("\"") && full.endsWith("\"")) {
                    full = full.substring(1, full.length() - 1);
                    String[] fullSplit = full.split(" ");
                    String[] abbrSplit = abbr.split("");
                    if (fullSplit.length == abbrSplit.length) {
                        boolean bool = true;
                        for (int i = 0; i < fullSplit.length; i++) {
                            if (!fullSplit[i].substring(0, 1).equals(abbrSplit[i])) {
                                bool = false;
                                break;
                            }
                        }
                        if (bool) {
//                            System.out.println("2");
                            return "true";
                        }
                    }
                }

                // 如果缩写不包含"-"，则可以把全写词组中的给予替换成空格，若包含，则不可以，比如：autosomal-dominant PKD(ADPKD)
                full = full.replace("-", " ").replace("(", "").replace(")", "").toLowerCase().replaceAll(" +", " ").trim();
                full = full.startsWith("\"") && full.endsWith("\"") ? full.substring(1, full.length() - 1) : full;
                abbr = abbr.replace("-", "").toLowerCase().replaceAll(" +", " ").trim();
                String[] fullSplit = full.split(" ");
                String[] abbrSplit = abbr.split("");
//                System.out.println("~~~full:" + full + " abbr:" + abbr + " fullSplit.length:" + fullSplit.length + " abbrSplit.length:" + abbrSplit.length);

                // leucine phenylalanine   leuphe
                if (fullSplit.length == 2 && !abbr.contains(" ")) {
                    for (int i = abbr.length(); i > 0; i--) {
                        if (fullSplit[0].startsWith(abbr.substring(0, i))) {
                            if (fullSplit[1].startsWith(abbr.substring(i))) {
                                return "true";
                            }
                        }
                    }
                }

                // 如果词组的个数与缩写单词的个数相同，且词组内各单词的首字母与缩写的单词的各个首字母相等，使用下面的方法。
                if (fullSplit.length == abbrSplit.length) {
                    boolean bool = true;
                    for (int i = 0; i < fullSplit.length; i++) {
                        if (!fullSplit[i].substring(0, 1).equals(abbrSplit[i])) {
                            bool = false;
                            break;
                        }
                    }
                    if (bool) {
//                        System.out.println("2");
                        return "true";
                    }
                }
                // full:nanosecond pulsed electric field abbr:nsPEF
                if (fullSplit.length == 4 && abbrSplit.length == 5) {
                    if (fullSplit[0].startsWith(abbrSplit[0]) && fullSplit[0].contains(abbrSplit[1]) && fullSplit[1].startsWith(abbrSplit[2]) && fullSplit[2].startsWith(abbrSplit[3]) && fullSplit[3].startsWith(abbrSplit[4])) {
//                        System.out.println("3");
                        return "true";
                    }
                    // full:|vascular smooth muscle cells| abbr:|VSMCs|
                    if (fullSplit[0].startsWith(abbrSplit[0]) && fullSplit[1].startsWith(abbrSplit[1]) && fullSplit[2].startsWith(abbrSplit[2]) && fullSplit[3].startsWith(abbrSplit[3]) && fullSplit[3].endsWith(abbrSplit[4])) {
//                        System.out.println("3.");
                        return "true";
                    }
                    //INTRODUCTION: Young pregnant and postpartum women living with HIV (WLHIV)
                    if (fullSplit[0].startsWith(abbrSplit[0]) && fullSplit[1].startsWith(abbrSplit[1]) && fullSplit[3].equals(abbrSplit[2] + abbrSplit[3] + abbrSplit[4])) {
//                        System.out.println("3.4");
                        return "true";
                    }
                }
                // Long non-coding RNA (lncRNA)
                if (fullSplit.length == 4 && abbrSplit.length == 6) {
                    if (fullSplit[0].startsWith(abbrSplit[0]) && fullSplit[1].startsWith(abbrSplit[1]) && fullSplit[2].startsWith(abbrSplit[2]) && fullSplit[3].equals(abbrSplit[3] + abbrSplit[4] + abbrSplit[5])) {
//                        System.out.println("3.3");
                        return "true";
                    }
                }

                // The effects of a diet enriched with 25% raw soya bean flour (RSF)
                if (fullSplit.length == 4 && abbrSplit.length == 3) {
                    if (fullSplit[0].startsWith(abbrSplit[0]) && fullSplit[1].startsWith(abbrSplit[1]) && fullSplit[3].startsWith(abbrSplit[2])) {
//                        System.out.println("3.4");
                        return "true";
                    }
                }

                // full:pancreatic neuroendocrine tumors abbr:PanNETs
                if (fullSplit.length == 3 && abbr.length() > 3) {
                    boolean bool = true;
                    int sum = 0;
                    T:
                    for (int i = abbr.length(); i > 0; i--) {
                        if (fullSplit[0].startsWith(abbr.substring(0, i))) {
                            // 第一次，abbr是完整的，第二次时，abbr变成了nets
                            String second = abbr.substring(i);
                            for (int j = second.length(); j > 0; j--) {
                                if (fullSplit[1].startsWith(second.substring(0, j))) {
                                    // 第三次时，abbr变成了ts
                                    String third = second.substring(j);
                                    String[] split = third.split("");
                                    // 如果全部包含，则维持bool仍为true，否则有任一个不是则为false。为什么要加一个变量sum？因为bool的默认值是true，如果程序走不到这一步呢？这样bool仍为true，加上sum后，要求其必须执行++，就可以确保程序走到了这一步。
                                    for (String s : split) {
                                        if (fullSplit[2].contains(s)) {
                                            sum++;
                                        } else {
                                            bool = false;
                                            break T;
                                        }
                                    }
                                    break T;
                                }
                            }
                        }
                    }
                    if (bool && sum > 0) {
//                        System.out.println("4");
                        return "true";
                    }
                }

                // We report herein a rare case of malignant gastrointestinal stromal tumor (GIST)
                if (fullSplit.length == 3 && abbrSplit.length == 4) {
                    if (fullSplit[0].startsWith(abbrSplit[0]) && fullSplit[0].contains(abbrSplit[1]) && fullSplit[1].startsWith(abbrSplit[2]) && fullSplit[2].startsWith(abbrSplit[3])) {
//                        System.out.println("4.1");
                        return "true";
                    }

                    // involvement of a ventral prefrontal cortex (VPFC)
                    if (fullSplit[0].startsWith(abbrSplit[0]) && fullSplit[1].startsWith(abbrSplit[1]) && fullSplit[1].contains(abbrSplit[2]) && fullSplit[2].startsWith(abbrSplit[3])) {
//                        System.out.println("4.2");
                        return "true";
                    }

                }
                // chemotherapy (CT)
                if (fullSplit.length == 1 && abbrSplit.length == 2) {
//                    System.out.println("5");
                    return fullSplit[0].startsWith(abbrSplit[0]) && fullSplit[0].contains(abbrSplit[1]) ? "true" : "false";
                }
                // full:lipopolysaccharide abbr:LPS
                if (fullSplit.length == 1 && abbrSplit.length == 3) {
//                    System.out.println("6");
                    return fullSplit[0].startsWith(abbrSplit[0]) && fullSplit[0].contains(abbrSplit[1]) && fullSplit[0].contains(abbrSplit[2]) ? "true" : "false";
                }
                // interquartile range (IQR)
                if (fullSplit.length == 2 && abbrSplit.length == 3) {
                    if (fullSplit[0].startsWith(abbrSplit[0])) {
                        if (fullSplit[0].contains(abbrSplit[1]) && fullSplit[1].startsWith(abbrSplit[2])) {
//                            System.out.println("7");
                            return "true";
                        }
                        if (fullSplit[1].startsWith(abbrSplit[1]) && fullSplit[1].contains(abbrSplit[2])) {
//                            System.out.println("8");
                            return "true";
                        }
                    }
                }

//                System.out.println("~~~full:" + full + " abbr:" + abbr + " fullSplit.length:" + fullSplit.length + " abbrSplit.length:" + abbrSplit.length);
                // the tissue non specific alkaline phosphatase (TNSALP)
                if (fullSplit.length == 5 && abbrSplit.length == 6) {
                    if (fullSplit[0].startsWith(abbrSplit[0]) && fullSplit[1].startsWith(abbrSplit[1]) && fullSplit[2].startsWith(abbrSplit[2]) && fullSplit[3].startsWith(abbrSplit[3]) && fullSplit[3].contains(abbrSplit[4]) && fullSplit[4].startsWith(abbrSplit[5])) {
//                        System.out.println("3.3");
                        return "true";
                    }
                    // ameliorates oxidative stress and dysfunctions of human retinal microvascular endothelial cells (hRMECs)
                    if (fullSplit[0].startsWith(abbrSplit[0]) && fullSplit[1].startsWith(abbrSplit[1]) && fullSplit[2].startsWith(abbrSplit[2]) && fullSplit[3].startsWith(abbrSplit[3]) && fullSplit[4].startsWith(abbrSplit[4]) && fullSplit[4].endsWith(abbrSplit[5])) {
//                        System.out.println("3.4");
                        return "true";
                    }
                }

                // ameliorates oxidative stress and dysfunctions of human retinal microvascular endothelial cells (hRMECs)

                // full:pseudorandom sinusoidal abbr:prss
                if (fullSplit.length == 2 && abbrSplit.length == 4) {
                    if (fullSplit[0].startsWith(abbrSplit[0]) && fullSplit[1].startsWith(abbrSplit[2])) {
                        if (fullSplit[0].contains(abbrSplit[1]) && fullSplit[1].contains(abbrSplit[3])) {
//                            System.out.println("9");
                            return "true";
                        }
                    }
                    // Considerable evidence has implicated matrix metalloproteinases (MMPs)
                    if (fullSplit[0].startsWith(abbrSplit[0]) && fullSplit[1].startsWith(abbrSplit[1]) && fullSplit[1].contains(abbrSplit[2]) && fullSplit[1].endsWith(abbrSplit[3])) {
//                        System.out.println("9.1");
                        return "true";
                    }
                }
                return isEquals(full, abbr);
            }
        }
        return "false";
    }


    // 功能：查看全写的词组（full）与缩写的词组（abbr）之间，是否匹配。采用的方法是先循环full，从前往后推，再循环abbr，如果各单词的首字母是，则isOk++，若不是则退出，最后若isOk >= abbrSplit.length则判定是。
    public static String isEquals(String full, String abbr) {
        full = full.replace("\"", "");
        String[] fullSplit = full.split(" ");
        String[] abbrSplit = abbr.split("");
        int fLength = fullSplit.length;
        int aLength = abbrSplit.length;
        String result = "";
        if (fLength > aLength) {
            for (int i = 0; i < fullSplit.length; i++) {
                int isOk = 0;
                StringBuilder str = new StringBuilder();
                for (int j = 0; j < abbrSplit.length; j++) {
                    String a = abbrSplit[j];
                    if (fullSplit.length > i + j) {
                        if (fullSplit[i + j].startsWith(a)) {
                            isOk++;
                            str.append(fullSplit[i + j]).append(" ");
                        } else {
                            isOk = 0;
                            break;
                        }
                    }
                }
                result = isOk >= abbrSplit.length ? str.toString().trim() : result;
            }
            // 上面的方法是直接匹配，不允许中间有介词，下面的方法允许有介词。
            if (result.isEmpty()) {
                int add = 0;
                for (int i = 0; i < fullSplit.length; i++) {
                    int isOk = 0;
                    StringBuilder str = new StringBuilder();
                    for (int j = 0; j < abbrSplit.length; j++) {
                        String a = abbrSplit[j];
                        if (fullSplit.length > add + i + j) {
                            String f = fullSplit[add + i + j];
                            if (("a".equals(f) || "an".equals(f) || "of".equals(f) || "in".equals(f) || "on".equals(f) || "the".equals(f) || "and".equals(f) || "is".equals(f) || "at".equals(f) || "for".equals(f) || "with".equals(f) ||
                                    "by".equals(f) || "from".equals(f) || "to".equals(f) || "between".equals(f) || "among".equals(f) || "under".equals(f) || "above".equals(f) || "voor".equals(f))) {
                                add += 1;
                                // 为什么大于2？in national institutes of health stroke scale->NIHSS
                                if (add > 2) {
                                    str.append(f).append(" ");
                                }
                            }
                            if (fullSplit.length > add + i + j) {
                                if (fullSplit[add + i + j].startsWith(a)) {
                                    str.append(fullSplit[add + i + j]).append(" ");
                                    isOk++;
                                } else {
                                    isOk = 0;
                                    break;
                                }
                            }
                        }
                    }
                    result = isOk >= abbrSplit.length ? str.toString().trim() : result;
                }
            }
        }
//        System.out.println("_____full:" + full + " abbr:" + abbr + " result:" + result);
        return result.isEmpty() ? "false" : result;
    }

    // 正则判断是否包含大小写英文字母
    public static boolean isEnglish(String str) {
        Pattern p = Pattern.compile("[a-zA-Z]");
        Matcher m = p.matcher(str);
        return m.find();
    }

    public static void insert(String fullAbbrKeyWordTable, String original, String full, String abbr, int isOk, int aid, Integer pmId, String ut) {
        try {
            Db.use().insert(Entity.create(fullAbbrKeyWordTable).set("original", original).set("full", full).set("abbr", abbr).set("is_ok", isOk).set("aid", aid).set("is_ok", isOk).set("pmId", pmId).set("ut", ut).set("aid", aid).set("date", LocalDateTime.now()));
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static Set<String> insertKeyWordSumMap(String keyWordSumTable, Map<List<String>, Integer> keyWordSumMap, Map<String, Integer> cnKeyWordSumMap, Map<String, String> lowerToUpperMap, Map<String, String> originalAndMeshMap) throws SQLException {
        Set<String> originalSet = new ConcurrentHashSet<>();
        Db.use().execute("truncate table " + keyWordSumTable);
        keyWordSumMap.keySet().parallelStream().forEach(keyWordList -> {
            Integer sum = keyWordSumMap.get(keyWordList);
            // 等于2说明是全写与缩写，全写为小写，缩写区分大小写，等于1说明是从原来的关键词字段中拿过来的，此时符合全写的全转成了小写，符合简写的，仍保留大小写，存在等于3的吗？不存在。
            String full = keyWordList.get(0);
            if ((keyWordList.size() == 2 && sum > 2)) {
                originalSet.add(full);
                originalSet.add(keyWordList.get(1));
            } else if (keyWordList.size() == 1 && sum > 5) {
                originalSet.add(full);
            }
            // 存表时再将小写转换成原来的区分大小写状态，keyWordList的尺寸只有1或2
            List<String> resultList = new ArrayList<>();
            String keyWord = keyWordList.get(0);
            resultList.add(lowerToUpperMap.getOrDefault(keyWord, keyWord));
            if (keyWordList.size() == 2) {
                resultList.add(keyWordList.get(1));
            }
            try {
                Db.use().insert(Entity.create(keyWordSumTable).set("key_word", JSONUtil.toJsonStr(resultList)).set("sum", sum).set("size", resultList.size()).set("is_cn", 0));
            } catch (SQLException e) {
                e.printStackTrace();
            }
        });
        keyWordSumMap.clear();
        cnKeyWordSumMap.keySet().parallelStream().forEach(cnKey -> {
            Integer sum = cnKeyWordSumMap.get(cnKey);
            try {
                Db.use().insert(Entity.create(keyWordSumTable).set("key_word", cnKey).set("sum", sum).set("is_cn", 1));
            } catch (SQLException e) {
                e.printStackTrace();
            }
            if ((cnKey.length() == 2 && sum > 20) || (cnKey.length() < 6 && cnKey.length() > 2 && sum > 10) || (cnKey.length() > 5 && sum > 5)) {
                originalSet.add(cnKey);
            }
        });
        for (Map.Entry<String, String> entry : originalAndMeshMap.entrySet()) {
            addOriginalSet(entry.getKey(), originalSet, lowerToUpperMap);
            addOriginalSet(entry.getValue(), originalSet, lowerToUpperMap);
        }
        cnKeyWordSumMap.clear();
        return originalSet;
    }

    public static void addOriginalSet(String keyWord, Set<String> originalSet, Map<String, String> lowerToUpperMap) {
        if (isKeyWordFull(keyWord)) {
            keyWord = keyWord.trim();
            keyWord = keyWord.endsWith(".") ? keyWord.substring(0, keyWord.length() - 1).trim() : keyWord;
            originalSet.add(keyWord);
            // 如果是全写的关键词，直接使用，如果不是，则再次检查是否是简写，如果是简写，则也使用。
        } else if (lowerToUpperMap.containsKey(keyWord)) {
            String upper = lowerToUpperMap.get(keyWord);
            if (isKeyWordAbbr(upper)) {
                originalSet.add(upper);
            }
        } else if (isKeyWordAbbr(keyWord)) {
            originalSet.add(keyWord);
        }
    }

    public static void readMesh(String readTable, String autoId, String field, Map<String, String> md5Map,
                                Map<String, String> keyWordAndMeshMap, Map<String, String> originalAndMeshMap, Map<String, String> reKwMap, Map<String, String> lowerToUpperMap) throws SQLException {
        int onePageNum = 1000;
        int tableMax = Db.use().queryNumber("select max(" + autoId + ") from " + readTable).intValue();
        if (tableMax > 0) {
            Collection<Integer> tableList = new ArrayList<>();
            NumberUtil.appendRange(0, tableMax + onePageNum, onePageNum, tableList);
            tableList.parallelStream().forEach(startId -> {
                try {
                    List<Entity> tableData = Db.use().query("select `" + autoId + "`,`" + field + "` from " + readTable + " where " + autoId + " between " + startId + " and " + (startId + onePageNum - 1));
                    for (Entity entry : tableData) {
                        String meshName = Utils.getStrField(entry, "meshName").replace("[Publication Type]", "").replace("[Subheading]", "").replace("[文献类型]", "").trim();
                        meshName = meshName.endsWith(".") ? meshName.substring(0, meshName.length() - 1).trim() : meshName;
                        List<String> kwList = strToList(Utils.getStrField(entry, "entryTerms"));
                        String meshLower = meshName.toLowerCase();
                        // todo gcr 这里没有验证 还是说这里不需要验证?  必须经验证（isKeyWordFull、isKeyWordAbbr）
                        lowerToUpperMap.putIfAbsent(meshLower, meshName);
                        kwList.add(meshLower);
                        for (String original : kwList) {
                            original = original.endsWith(".") ? original.substring(0, original.length() - 1).trim() : original.trim();
                            if (isKeyWordFull(original)) {
                                String lower = original.toLowerCase();
                                lowerToUpperMap.putIfAbsent(lower, original);
                                originalAndMeshMap.put(lower, meshLower);
                            } else if (isKeyWordAbbr(original)) {
                                originalAndMeshMap.put(original, meshLower);
                            }
                            // 2025.04.20 取消下面的代码，使用上面的代码，原因是符合全写的转成小写，不符合的全写但符合简写的，仍保留大小写。
//                            if (isKeyWordFull(original) || isKeyWordAbbr(original)) {
//                                String lower = original.toLowerCase();
//                                lowerToUpperMap.putIfAbsent(lower, original);
//                                originalAndMeshMap.put(lower, meshLower);
//                            }
                        }
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    // 字符串转换为List后输出，参数len是对字符串长度的约束，一般来说List存表后的字符串应大于4位，比如：["图书馆"]
    public static List<String> strToList(String str) {
        if (null != str && str.length() > 4 && str.endsWith("\"]")) {
            try {
                List<String> list = JSONObject.parseArray(str, String.class);
                if (null != list && !list.isEmpty()) {
                    Set<String> set = new HashSet<>();
                    for (String s : list) {
                        if (!s.isEmpty()) {
                            set.add(s);
                        }
                    }
                    return new ArrayList<>(set);
                }
            } catch (Exception e) {
                System.out.println("\033[31;1m" + "--strToList err-- " + "\033[0m" + str);
            }
        }
        return new ArrayList<>();
    }

    public static En.AbKeyMethod getTransStr(Set<String> backgroundSet, Set<String> discussionSet, Set<String> methodsSet, Set<String> resultsSet, List<En.Trans> transList) {
        List<En.Trans> backgroundList = new ArrayList<>();
        List<En.Trans> discussionList = new ArrayList<>();
        List<En.Trans> methodsList = new ArrayList<>();
        List<En.Trans> resultsList = new ArrayList<>();
        for (En.Trans trans : transList) {
            String l = trans.getLabel().toLowerCase().trim();
            // 为什么作下面的两行？原因是部分pm的存在序号，比如：1) PURPOSE: To demonstrate，而这个序号在往标签中添加时，并没有去掉，但在标签集合中，是没有这着序号的，因此需要先去掉再使用。
            l = (l.startsWith("1)") || l.startsWith("2)") || l.startsWith("3)") || l.startsWith("4)") || l.startsWith("5)") || l.startsWith("6)") || l.startsWith("7)") || l.startsWith("8)") || l.startsWith("9)")) ? l.substring(2).trim() : l;
            l = (l.startsWith("(1)") || l.startsWith("(2)") || l.startsWith("(3)") || l.startsWith("(4)") || l.startsWith("(5)") || l.startsWith("(6)") || l.startsWith("(7)") || l.startsWith("(8)") || l.startsWith("(9)")) ? l.substring(3).trim() : l;
            l = l.toLowerCase().trim();
            l = l.endsWith(":") ? l.substring(0, l.length() - ":".length()).trim() : l;
            l = l.endsWith(".") ? l.substring(0, l.length() - ".".length()).trim() : l;
            if (backgroundSet.contains(l)) {
                backgroundList.add(trans);
            }
            if (discussionSet.contains(l)) {
                discussionList.add(trans);
            }
            if (methodsSet.contains(l)) {
                methodsList.add(trans);
            }
            if (resultsSet.contains(l)) {
                resultsList.add(trans);
            }
        }
        En.AbKeyMethod abKeyMethod = new En.AbKeyMethod();
        abKeyMethod.setBackground(JSONUtil.toJsonStr(backgroundList));
        abKeyMethod.setDiscussion(JSONUtil.toJsonStr(discussionList));
        abKeyMethod.setMethods(JSONUtil.toJsonStr(methodsList));
        abKeyMethod.setResults(JSONUtil.toJsonStr(resultsList));
        return abKeyMethod;
    }

    public static List<En.Trans> transAbstract(String ab, List<String> originalWordList, Set<String> smallWordSet) {
        List<En.Trans> transList = new ArrayList<>();
        if (null != ab && ab.length() > 3) {
            String[] split = ab.trim().split("</p>");
            for (String s : split) {
                s = s.startsWith("<p>") ? s.substring("<p>".length()) : s;
                if (s.contains("<strong class=")) {
                    if (s.contains("<strong class=\"sub-title\">") && s.contains("</strong>")) {
                        s = s.replace("<strong class=\"sub-title\">", " ").replace("</strong>", "âãä").replaceAll(" +", " ").trim();
                    }
                    transList.add(addTrans(s));
                } else {
                    String t = ". " + s.replace("：", ":").replace("。", ". ").replace("（", "(").replace("）", ")").replaceAll(" +", " ").trim();
                    t = addLabel(t, originalWordList, smallWordSet);
                    String[] strings = t.split("ôõö");
                    if (strings.length <= 2) {
                        transList.add(addTrans(t.replace("ôõö", "").replace("âãä", "")));
                    } else {
                        for (String string : strings) {
                            if (string.replace(".", "").trim().length() > 1) {
                                transList.add(addTrans(string));
                            }
                        }
                    }
                }
            }
        }
        return transList;
    }

    // originalWordList是从长到短的，匹配上最长的，即停止。simpWordSet是出现次数在一万条以上的，仅127条，全部为小写。
    public static String addLabel(String str, List<String> originalWordList, Set<String> smallWordSet) {
        String[] split = str.split("\\.");
        StringBuilder r = new StringBuilder();
        boolean isColon = isColon(str, smallWordSet);
        boolean isCapital = isCapital(str, smallWordSet);
        boolean isBlank = isBlank(str, smallWordSet);
        boolean isBracket = isBracket(str, smallWordSet);
        for (String s : split) {
            String f = s.startsWith(" ") ? " " : ""; // 如果有空格，那么，仍然保留。
            s = s.trim();
            boolean notFind = true; // 如果没有匹配上，则直接添加到结果中
            if (!s.isEmpty()) {
                for (String o : originalWordList) {
                    o = o.toLowerCase();
                    if (isBracket) { // 1) PURPOSE: To demonstrate 针对有序号的
                        for (int i = 1; i < 10; i++) {
                            if (s.toLowerCase().startsWith(i + ") " + o + ": ")) {
                                int len = o.length() + ") ".length() + Integer.toString(i).length();
                                r.append(".").append(f).append("ôõö").append(s, 0, len).append("âãä").append(s.substring(len));
                                notFind = false;
                                break;
                            } else if (s.toLowerCase().startsWith(i + ")" + o + ": ")) {
                                int len = o.length() + ")".length() + Integer.toString(i).length();
                                r.append(".").append(f).append("ôõö").append(s, 0, len).append("âãä").append(s.substring(len));
                                notFind = false;
                                break;
                            } else if (s.toLowerCase().startsWith("(" + i + ") " + o + ": ")) {
                                int len = o.length() + ("(" + i + ") ").length();
                                r.append(".").append(f).append("ôõö").append(s, 0, len).append("âãä").append(s.substring(len));
                                notFind = false;
                                break;
                            } else if (s.toLowerCase().startsWith("(" + i + ")" + o + ": ")) {
                                int len = o.length() + ("(" + i + ")").length();
                                r.append(".").append(f).append("ôõö").append(s, 0, len).append("âãä").append(s.substring(len));
                                notFind = false;
                                break;
                            }
                        }
                    } else if (s.toLowerCase().startsWith(o) && s.length() > o.length() + 1) {
                        String e1 = s.substring(o.length(), o.length() + 1);
                        String e2 = s.substring(o.length() + 1, o.length() + 2);
                        if (isColon) { // 如果最常用的一些标签后面含有冒号加空格，那么就认定本摘要是这种格式的，就不认冒号后面没有空格的为标签了。此方法仅127个出现一万次以上的标签，会漏掉一些，影响不大。
                            if (":".equals(e1) && " ".equals(e2)) { // 标签后的那个字符，如果是大写字母或冒号或句号，则认为是标签，如果是标签，就要加冒号
                                r.append(".").append(f).append("ôõö").append(s, 0, o.length()).append("âãä").append(s.substring(o.length()));
                                notFind = false;
                                break;
                            }
                        } else if (isCapital) { // 如果是标签后面紧跟着大写字母或数字，而不是冒号、句号或空格，这种仅限于出现次数在一万条以上的标签。
                            if (smallWordSet.contains(o) && Pattern.compile("[A-Z0-9]").matcher(e1).find()) { // 标签后的那个字符，如果是大写字母或冒号或句号，则认为是标签，如果是标签，就要加冒号
                                r.append(".").append(f).append("ôõö").append(s, 0, o.length()).append("âãä").append(s.substring(o.length()));
                                notFind = false;
                                break;
                            }
                        } else if (isBlank) { // 这种是没有冒号，而是标签后面有一个空格，空格后面必须又是一个大写的字母，但仅限于出现次数在一万条以上的标签。
                            if (smallWordSet.contains(o) && " ".equals(e1) && Pattern.compile("[A-Z]").matcher(e2).find()) { // 标签后的那个字符，如果是大写字母或冒号或句号或空格，则认为是标签，如果是标签，就要加冒号
                                r.append(".").append(f).append("ôõö").append(s, 0, o.length()).append("âãä").append(s.substring(o.length()));
                                notFind = false;
                                break;
                            }
                        } else { // 其它的，都走这种方法了，即标签后可以是大写字母，也可以是冒号，也可以是句号，但不能是空格。
                            if (Pattern.compile("[A-Z:.]").matcher(e1).find()) { // 标签后的那个字符，如果是大写字母或冒号或句号，则认为是标签，如果是标签，就要加冒号
                                r.append(".").append(f).append("ôõö").append(s, 0, o.length()).append("âãä").append(s.substring(o.length()));
                                notFind = false;
                                break;
                            }
                        }
                    }
                }
            }
            if (notFind) {
                r.append(".").append(f).append(s);
            }
        }
        return r.toString();
    }

    // 这种是标签后面冒号加空格型的
    public static boolean isColon(String str, Set<String> smallWordSet) {
        String n = str.replace(". ", ".").toLowerCase();
        for (String s : smallWordSet) {
            if (n.contains("." + s.toLowerCase() + ": ")) {
                return true;
            }
        }
        return false;
    }

    // 1) PURPOSE: To demonstrate 针对有序号的
    public static boolean isBracket(String str, Set<String> smallWordSet) {
        String n = str.replace("（", "(").replace(". ", ".").replace("）", ")").toLowerCase();
        int sum = 0;
        for (String s : smallWordSet) {
            for (int i = 1; i < 10; i++) {
                if (n.contains("." + i + ") " + s.toLowerCase() + ": ") || n.contains(".(" + i + ") " + s.toLowerCase() + ": ")) {
                    sum++;
                }
            }
        }
        return sum > 1;
    }

    // 这种是没有冒号，而是标签后面有一个空格，空格后面必须又是一个大写的字母，但仅限于出现次数在一万条以上的标签。
    public static boolean isBlank(String str, Set<String> smallWordSet) {
        String[] split = str.split("\\.");
        int sum = 0;
        for (String s : split) {
            s = s.trim();
            for (String o : smallWordSet) {
                if (s.toLowerCase().startsWith(o.toLowerCase()) && s.length() > o.length() + 1 && o.trim().length() > 2) {
                    String e1 = s.substring(o.length(), o.length() + 1);
                    String e2 = s.substring(o.length() + 1, o.length() + 2);
                    if (" ".equals(e1) && Pattern.compile("[A-Z]").matcher(e2).find()) {
                        sum++;
                    }
                }
            }
        }
        return sum > 1;
    }

    // 如果是标签后面紧跟着大写字母，而不是冒号、句号或空格，这种仅限于出现次数在一万条以上的标签。
    public static boolean isCapital(String str, Set<String> smallWordSet) {
        String[] split = str.split("\\.");
        int sum = 0;
        for (String s : split) {
            s = s.trim();
            for (String o : smallWordSet) {
                if (s.toLowerCase().startsWith(o.toLowerCase()) && s.length() > o.length() && o.trim().length() > 2) {
                    String e = s.substring(o.length(), o.length() + 1);
                    if (Pattern.compile("[A-Z]").matcher(e).find()) { // 标签后的那个字符，如果是大写字母，则认为是标签，如果是标签，就要加冒号
                        sum++;
                    }
                }
            }
        }
        return sum > 1;
    }

    public static En.Trans addTrans(String str) {
        En.Trans trans = new En.Trans();
        for (int i = 0; i < str.length(); i++) {
            str = str.trim();
            if (str.startsWith(".")) {
                str = str.substring(".".length()).trim();
            } else if (str.endsWith(".")) {
                str = str.substring(0, str.length() - ".".length()).trim();
            } else {
                break;
            }
        }
        str = !str.endsWith(".") ? str + ". " : str;
        if (!Utils.isContainChinese(str)) {
            if (str.contains("âãä") && str.length() > 5) {
                String label = str.substring(0, str.indexOf("âãä")).trim();
                label = label.endsWith(":") ? label.substring(0, label.length() - ":".length()) : label;
                String en = str.substring(str.indexOf("âãä") + "âãä".length()).trim();
                en = en.startsWith(":") ? en.substring(":".length()).trim() : en;
                trans.setLabel(label);
                trans.setEn(en);
//                trans.setCn(StudyUtils.enToCnTrans((label + ": " + en)).replace("::", ":"));
            } else if (str.trim().length() > 1) {
                trans.setEn(str);
//                trans.setCn(StudyUtils.enToCnTrans(str));
            }
        }
        return trans;
    }

    public static void addKeySet(String keyword, Set<List<String>> keySet, Map<List<String>, Integer> keyWordSumMap, Map<String, Integer> cnKeyWordSumMap, Map<String, String> lowerToUpperMap) {
        if (null != keyword && keyword.length() > 2) {
            String[] split = keyword.replace("（", "(").replace("）", ")").replace("；", "; ").replace("，", ",").replace("。", "; ")
                    .replaceAll(" +", " ").replace("( ", "(").replace(" )", ")").replace(" ,", ",").replace(", ", ",").split("; ");
            // 2025.04.19，如果所有的英文的单词，不含有小写的字母，则不认为是大写的词组，即必须含有小写字母，才可以认定是词组。
            boolean isWord = Pattern.compile("[a-z]").matcher(keyword).find();
            for (String keyWord : split) {
                if (Utils.isContainChinese(keyWord)) {
                    if (keyWord.trim().length() > 1) {
                        // 内(Fracturefixation,internal)骨折固定术->内骨折固定术;(Fracturefixation,internal)
                        String str = keyWord.replaceAll("(.*)(\\(.*\\))(.*)", "$1$3ĺļ$2");
                        if (str.contains("ĺļ")) {
                            String[] strSplit = str.split("ĺļ");
                            if (strSplit.length == 2) {
                                String s = strSplit[1].trim();
                                s = s.startsWith("(") && s.endsWith(")") ? s.substring("(".length(), s.length() - ")".length()) : s;
                                // 如果括号内是中文，忽略括号。如果是英文，再检查是全写，还是简写，如果符合要求，加入。
                                if (Utils.isContainChinese(s)) {
                                    lowerToUpperMap.putIfAbsent(str.toLowerCase(), str);
                                    cnKeyWordSumMap.compute(str, (key, value) -> value != null ? (value + 1) : 1);
                                } else {
                                    String t = strSplit[0].trim();
                                    lowerToUpperMap.putIfAbsent(t.toLowerCase(), t);
                                    cnKeyWordSumMap.compute(t, (key, value) -> value != null ? (value + 1) : 1);
                                    if (isWord) {
                                        putKeyWordSumMap(s, keySet, keyWordSumMap, lowerToUpperMap);
                                    }
                                }
                            } else {
                                String t = str.replace("ĺļ", "").trim();
                                lowerToUpperMap.putIfAbsent(t.toLowerCase(), t);
                                cnKeyWordSumMap.compute(t, (key, value) -> value != null ? (value + 1) : 1);
                            }
                        } else {
                            String t = keyWord.trim();
                            lowerToUpperMap.putIfAbsent(t.toLowerCase(), t);
                            cnKeyWordSumMap.compute(t, (key, value) -> value != null ? (value + 1) : 1);
                        }
                    }
                } else if (isWord) {
                    putKeyWordSumMap(keyWord, keySet, keyWordSumMap, lowerToUpperMap);
                }
            }
        }
    }

    public static void putKeyWordSumMap(String enKeyWord, Set<List<String>> keySet, Map<List<String>, Integer> keyWordSumMap, Map<String, String> lowerToUpperMap) {
        enKeyWord = enKeyWord.trim();
        enKeyWord = enKeyWord.endsWith(".") ? enKeyWord.substring(0, enKeyWord.length() - 1).trim() : enKeyWord;
        List<String> fullAbbrList = new ArrayList<>();
        if (isKeyWordFull(enKeyWord)) {
            // 如果lowerToUpperMap中不含有keyWord的小写所对应的keyWord的原值，则给put进去，且fullAbbrList只添加keyWord的原值。
            String lower = enKeyWord.toLowerCase();
            lowerToUpperMap.putIfAbsent(lower, enKeyWord);
            fullAbbrList.add(lower);
            keySet.add(fullAbbrList);
            keyWordSumMap.compute(fullAbbrList, (key, value) -> value != null ? (value + 1) : 1);
        } else if (isKeyWordAbbr(enKeyWord)) {
            fullAbbrList.add(enKeyWord);
            keySet.add(fullAbbrList);
            keyWordSumMap.compute(fullAbbrList, (key, value) -> value != null ? (value + 1) : 1);
        } else if (enKeyWord.length() > 1) {
            // 为什么有这一步操作？因为后续的还原，凡是关键词，即使不符合全写也不符合简写，也要有映射关系，用于还原。
            lowerToUpperMap.putIfAbsent(enKeyWord.toLowerCase(), enKeyWord);
        }
        // 2025.04.20 取消下面的代码，使用上面的代码，原因是全写的关键词，全部是使用小写的，简写的关键词，是区分大小写的。
//        if (isKeyWordFull(enKeyWord) || isKeyWordAbbr(enKeyWord)) {
//            enKeyWord = enKeyWord.trim();
//            enKeyWord = enKeyWord.endsWith(".") ? enKeyWord.substring(0, enKeyWord.length() - 1).trim() : enKeyWord;
//            List<String> fullAbbrList = new ArrayList<>();
//            // 如果lowerToUpperMap中不含有keyWord的小写所对应的keyWord的原值，则给put进去，且fullAbbrList只添加keyWord的原值。
//            String lower = enKeyWord.toLowerCase();
//            lowerToUpperMap.putIfAbsent(lower, enKeyWord);
//            fullAbbrList.add(lower);
//            keySet.add(fullAbbrList);
//            keyWordSumMap.compute(fullAbbrList, (key, value) -> value != null ? (value + 1) : 1);
//        }
    }

    // 全写的关键词的认定标准，要求至少6位，且少于65位，且以空格切割小于10个单词。anaplastic lymphoma kinase-positive nonsmall cell lung cancer 这个字符串为61位
    public static boolean isKeyWordFull(String full) {
        String l = null == full ? "" : full.toLowerCase().trim();
        if ((l.length() > 5 && l.contains(" ")) || (l.length() > 11 && l.length() < 65 && l.replace("-", " ").split(" ").length < 10)) {
            return !l.equals("in order to") && !l.equals("as well as") && !l.equals("such as") && !l.equals("due to") && !l.equals("because of") && !l.equals("in case") && !l.equals("in addition") && !l.equals("on the other hand") && !l.equals("at least") &&
                    !l.equals("at most") && !l.equals("by means of") && !l.equals("in terms of") && !l.equals("with respect to") && !l.startsWith("the ") && !l.startsWith("and ") && !l.startsWith("in ") && !l.startsWith("of ") && !l.startsWith("at ") &&
                    !l.startsWith("to ") && !l.startsWith("on ") && !l.startsWith("this ") && !l.startsWith("from ") && !l.startsWith("- ") && !l.startsWith("it is ") && !l.startsWith("by ") && !l.startsWith("up ") && !l.startsWith("or ") && !l.startsWith("but ") &&
                    !l.startsWith("as ") && !l.startsWith("if ") && !l.startsWith("because ") && !l.startsWith("so ") && !l.startsWith("that ") && !l.startsWith("which ") && !l.startsWith("who ") && !l.startsWith("whose ") && !l.startsWith("where ") &&
                    !l.startsWith("when ") && !l.startsWith("why ") && !l.startsWith("how ") && !l.endsWith(" the") && !l.endsWith(" and") && !l.endsWith(" in") && !l.endsWith(" of") && !l.endsWith(" at") && !l.endsWith(" to") && !l.endsWith(" on") &&
                    !l.endsWith(" this") && !l.endsWith(" from") && !l.endsWith(" -") && !l.endsWith(" it is") && !l.endsWith(" by") && !l.endsWith(" up") && !l.endsWith(" or") && !l.endsWith(" but") && !l.endsWith(" as") && !l.endsWith(" if") &&
                    !l.endsWith(" because") && !l.endsWith(" so") && !l.endsWith(" that") && !l.endsWith(" which") && !l.endsWith(" who") && !l.endsWith(" whose") && !l.endsWith(" where") && !l.endsWith(" when") && !l.endsWith(" why") && !l.endsWith(" how");
        }
        return false;
    }

    // 如果缩写的单词为3位，要求必须全部是大写，且不含数字和空格，如果是4位或以上，则要求除首位外其它必须得有一个大写字母或包含"-"才可以。缩写的区分大小写，即6位以下的。
    public static boolean isKeyWordAbbr(String abbr) {
        if (null == abbr || abbr.length() < 3) {
            return false;
        }
        String l = abbr.toLowerCase().trim();
        if (l.equals("the") || l.equals("and") || l.equals("this") || l.equals("from") || l.equals("it is") || l.equals("about") || l.equals("above") || l.equals("across") || l.equals("after") || l.equals("against") || l.equals("along") || l.equals("among") ||
                l.equals("around") || l.equals("before") || l.equals("behind") || l.equals("below") || l.equals("beneath") || l.equals("beside") || l.equals("between") || l.equals("down") || l.equals("during") || l.equals("except") || l.equals("for") ||
                l.equals("inside") || l.equals("into") || l.equals("like") || l.equals("near") || l.equals("off") || l.equals("onto") || l.equals("out") || l.equals("outside") || l.equals("over") || l.equals("past") || l.equals("since") || l.equals("through") ||
                l.equals("throughout") || l.equals("toward") || l.equals("under") || l.equals("underneath") || l.equals("until") || l.equals("upon") || l.equals("with") || l.equals("within") || l.equals("without") || l.equals("are") || l.equals("was") ||
                l.equals("were") || l.equals("been") || l.equals("has") || l.equals("have") || l.equals("had") || l.equals("does") || l.equals("did") || l.equals("will") || l.equals("would") || l.equals("shall") || l.equals("should") || l.equals("can") ||
                l.equals("could") || l.equals("may") || l.equals("might") || l.equals("must") || l.equals("in order to") || l.equals("as well as") || l.equals("such as") || l.equals("due to") || l.equals("because of") || l.equals("in case") || l.equals("in addition") ||
                l.equals("on the other hand") || l.equals("at least") || l.equals("at most") || l.equals("by means of") || l.equals("in terms of") || l.equals("with respect to") || l.startsWith("the ") || l.startsWith("and ") || l.startsWith("in ") ||
                l.startsWith("of ") || l.startsWith("at ") || l.startsWith("to ") || l.startsWith("on ") || l.startsWith("this ") || l.startsWith("from ") || l.startsWith("- ") || l.startsWith("it is ") || l.startsWith("by ") || l.startsWith("up ") ||
                l.startsWith("or ") || l.startsWith("but ") || l.startsWith("as ") || l.startsWith("if ") || l.startsWith("because ") || l.startsWith("so ") || l.startsWith("that ") || l.startsWith("which ") || l.startsWith("who ") || l.startsWith("whose ") ||
                l.startsWith("where ") || l.startsWith("when ") || l.startsWith("why ") || l.startsWith("how ") || l.endsWith(" the") || l.endsWith(" and") || l.endsWith(" in") || l.endsWith(" of") || l.endsWith(" at") || l.endsWith(" to") || l.endsWith(" on") ||
                l.endsWith(" this") || l.endsWith(" from") || l.endsWith(" -") || l.endsWith(" it is") || l.endsWith(" by") || l.endsWith(" up") || l.endsWith(" or") || l.endsWith(" but") || l.endsWith(" as") || l.endsWith(" if") || l.endsWith(" because") ||
                l.endsWith(" so") || l.endsWith(" that") || l.endsWith(" which") || l.endsWith(" who") || l.endsWith(" whose") || l.endsWith(" where") || l.endsWith(" when") || l.endsWith(" why") || l.endsWith(" how")) {
            return false;
        }
        if (abbr.length() < 4) {
            // 2025.04.19 如果等于3位，原来要求必须三位都是大写，现在要求第2位为大写，且不得包含数字或空格。
            return Pattern.compile("[A-Z]").matcher(abbr.substring(1)).find() && !Pattern.compile("[0-9 ]").matcher(abbr).find();
        } else {
            return Pattern.compile("[A-Z]").matcher(abbr.substring(1)).find() || abbr.contains("-");
        }
    }


}
